package model

import (
	"errors"
	"fmt"
	"gitee.com/tang_q/gin-vue3-admin-api/global"
	"gitee.com/tang_q/gin-vue3-admin-api/model/dto"
	"gitee.com/tang_q/gin-vue3-admin-api/utils"
	"gorm.io/gorm"
)

type Menus struct {
	gorm.Model
	Name        string  `form:"name" json:"name" gorm:"comment:菜单名称"`
	RouteName   string  `form:"route_name" json:"route_name" gorm:"comment:路由名称"`
	URL         string  `form:"url" json:"url" gorm:"comment:路由地址"`
	Component   string  `form:"component" json:"component" gorm:"comment:组件路径"`
	RequestType string  `form:"request_type" json:"request_type" gorm:"comment:请求类型"`
	Icon        string  `form:"icon" json:"icon" gorm:"comment:图标"`
	ParentID    uint    `form:"parent_id" json:"parent_id" gorm:"comment:父节点ID"`
	ParentName  string  `form:"parent_name" json:"parent_name" gorm:"comment:父节点名称"`
	Type        uint8   `form:"type" json:"type" gorm:"comment:类型 1:菜单 2:权限 3:API"`
	Permissions string  `form:"permissions" json:"permissions" gorm:"comment:权限标识"`
	Level       uint8   `form:"level" json:"level" gorm:"comment:层级"`
	Sort        uint    `form:"order" json:"sort" gorm:"comment:排序"`
	Roles       []Roles `gorm:"many2many:roles_menus;" gorm:"comment:菜单名称" json:"-"`
}

// init 是Go语言中的一个特殊函数，它在程序启动时被自动调用。
// 此函数用于初始化Menus表，使用GORM的AutoMigrate函数进行自动迁移。
// 如果迁移过程中发生错误，将会记录错误信息到global.Logger，并返回。
func init() {
	err := global.DB.AutoMigrate(&Menus{})
	if err != nil {
		global.Logger.Error("Menus表自动迁移菜单表失败!", err)
		return
	}
}

func (m *Menus) BeforeCreate(*gorm.DB) error {
	if m.ParentName == "首页" && m.Type == 1 {
		return fmt.Errorf("首页菜单不能添加子菜单")
	}
	return nil
}

// GetAllMenus 获取所有菜单
func GetAllMenus() (menus []Menus, err error) {
	err = global.DB.Model(Menus{}).Where("type = ?", 1).Order("sort asc").Find(&menus).Error
	return
}

// BuildMenuRouteTree 根据给定的menus切片和初始DTO对象构建菜单路由树，并返回根节点的子节点切片。
// DTO: 初始的菜单路由DTO对象，用于作为树的根节点。
// menus: 包含所有菜单信息的Menus切片。
// 返回值: []*dto.MenuRouteDTO，表示根节点的子节点切片。
func BuildMenuRouteTree(DTO *dto.MenuRouteDTO, menus []Menus) []*dto.MenuRouteDTO {
	processedIDs := make(map[uint]bool) // 创建一个映射来存储已处理的菜单ID
	for _, menu := range menus {
		if menu.ParentID == DTO.ID && !processedIDs[menu.ID] { // 检查菜单ID是否已处理
			processedIDs[menu.ID] = true
			childMenuRoute := &dto.MenuRouteDTO{
				ID:        menu.ID,
				Path:      menu.URL,
				Name:      menu.RouteName,
				Redirect:  "",
				Component: menu.Component,
				Meta: dto.Meta{
					Title: menu.Name,
					Icon:  menu.Icon,
				},
				Children: []*dto.MenuRouteDTO{},
			}
			BuildMenuRouteTree(childMenuRoute, menus)
			DTO.Children = append(DTO.Children, childMenuRoute)
		}
	}
	return DTO.Children
}

// GetMenusList 函数用于从数据库中查询所有菜单列表，并按照层级和排序顺序进行排序。
// 返回值：
// menus：包含所有菜单信息的Menus切片。
// err：如果在查询过程中发生错误，则返回非零的错误码；否则返回nil。
func GetMenusList() (menus []Menus, err error) {
	err = global.DB.Model(&menus).Order("level asc").Order("sort asc").Find(&menus).Error
	return
}

// GetMenusListByType 只获取类型为1和2的菜单
func GetMenusListByType() (menus []Menus, err error) {
	err = global.DB.Model(&menus).Where("type in (?)", []uint{1, 2}).Order("level asc").Order("sort asc").Find(&menus).Error
	return
}

// CreateMenu 方法用于根据给定的CreateMenuDTO数据创建一个新的菜单记录
// 参数：
//   - data dto.CreateMenuDTO：包含要创建菜单的详细信息的CreateMenuDTO结构体
//
// 返回值：
//   - error：如果创建过程中发生错误，则返回非零的错误码；否则返回nil
func (m *Menus) CreateMenu(data dto.CreateMenuDTO) error {
	menus := &Menus{Level: 1}
	sort, err := menus.getNextSort()
	if err != nil {
		return err
	}
	m.Name = data.Name
	m.URL = data.URL
	m.RouteName = data.RouteName
	m.Component = data.Component
	m.Icon = data.Icon
	m.Level = 1
	m.Type = 1
	m.Sort = sort
	if err := global.DB.Create(m).Error; err != nil {
		return err
	}
	return nil
}

// GetMenusByIds 根据ids获取菜单列表
func GetMenusByIds(ids []uint) (menus []Menus, err error) {
	if len(ids) == 0 {
		return menus, nil
	}
	err = global.DB.Model(&menus).Where("id in (?)", ids).Find(&menus).Error
	return
}

// GetAPIByIds 根据dis获取所有api
func GetAPIByIds(ids []uint) (menus []Menus, err error) {
	err = global.DB.Where("type = ?", 3).Where("parent_id in (?)", ids).Find(&menus).Error
	return
}

// getNextSort 获取下一个排序
func (m *Menus) getNextSort() (uint, error) {
	if global.DB.Model(m).Where("parent_id", m.ParentID).Order("sort desc").Take(m).Error != nil {
		return 1, fmt.Errorf("获取下一个排序失败") // 出现错误说明没有数据，然后返回1
	}
	return m.Sort + 1, nil
}

// AddChildItem 添加子项。
// 该方法接收一个AddMenuOrAPIDTO类型的data参数，包含了新增菜单或API的所有必要信息。
// 返回值是一个error，用于表示添加过程中是否发生了错误。
func (m *Menus) AddChildItem(data dto.AddMenuOrAPIDTO) error {
	// 初始化一个新的Menus实例，用于后续的数据填充和数据库插入操作。
	menus := &Menus{ParentID: data.ParentID}

	// 获取下一个排序值，用于确保新添加的菜单或API有一个唯一的排序编号。
	sort, _ := menus.getNextSort()

	// 从data参数中逐一提取信息，并赋值给当前Menus实例的相应字段。
	m.Name = data.Name
	m.URL = data.URL
	m.RouteName = data.RouteName
	m.Component = data.Component
	m.Icon = data.Icon
	m.Level = data.Level
	m.ParentName = data.ParentName
	m.ParentID = data.ParentID
	m.Permissions = data.Permissions

	// 将data中的Type字段转换为uint8类型，并赋值给当前Menus实例的Type字段。
	m.Type, _ = utils.StringToUint8(data.Type)

	// 设置排序值。
	m.Sort = sort

	// 从data参数中获取请求类型，并赋值给当前Menus实例的RequestType字段。
	m.RequestType = data.RequestType

	// 使用全局数据库实例，将当前Menus实例插入到数据库中。如果插入过程中发生错误，则返回该错误。
	if err := global.DB.Create(m).Error; err != nil {
		return err
	}

	// 如果插入操作成功，则返回nil，表示没有发生错误。
	return nil
}

func (m *Menus) Update(data dto.EditMenuDTO) error {
	if err := global.DB.First(m, data.ID).Error; err != nil {
		return err
	}
	m.Name = data.Name
	m.URL = data.URL
	m.RouteName = data.RouteName
	m.Component = data.Component
	m.Icon = data.Icon
	m.RequestType = data.RequestType
	m.Sort = data.Sort
	m.ParentName = data.ParentName
	m.Permissions = data.Permissions
	if err := global.DB.Save(m).Error; err != nil {
		return err
	}
	return nil
}

// DeleteMenu 函数用于删除指定ID的菜单，并递归删除其子菜单。
func DeleteMenu(menuID uint) error {
	return global.DB.Transaction(func(tx *gorm.DB) error {
		var menu Menus
		if err := tx.First(&menu, menuID).Error; err != nil {
			return err
		}

		// Find all child menus
		var childMenus []Menus
		if err := tx.Where("parent_id = ?", menuID).Find(&childMenus).Error; err != nil {
			return err
		}

		// Recursively delete child menus
		for _, childMenu := range childMenus {
			if err := DeleteMenu(childMenu.ID); err != nil {
				return err
			}
		}

		// Remove role associations
		if err := tx.Model(&menu).Association("Roles").Clear(); err != nil {
			return err
		}

		// Delete the menu
		if err := tx.Unscoped().Delete(&menu).Error; err != nil {
			return err
		}
		// 如果删除的是api就对应删除api的casbin策略 并且有对应的策略则删除
		if menu.Type == 3 && utils.GetPermissionForResource(menu.URL) {
			resource, err := utils.RemovePermissionForResource(menu.URL)
			if err != nil {
				return err
			}
			if !resource {
				return errors.New("删除失败")
			}
		}

		return nil
	})
}
